package cn.tqfeiyang.springplus.framework.pagination;

import cn.tqfeiyang.springplus.framework.conversion.ConversionService;
import cn.tqfeiyang.springplus.framework.toolkit.ContextUtils;
import cn.tqfeiyang.springplus.framework.toolkit.ServletUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.List;
import java.util.function.Function;

public class PageHelper<T> {

    /**
     * 页面请求对象
     */
    private PageSearch pageSearch;

    public PageHelper(PageSearch pageSearch) {
        this.pageSearch = pageSearch;
    }

    public <D> PageData<D> selectPage(BaseMapper<T> entityMapper, Function<T, ? extends D> mapperFunction) {
        Page<T> page = this.getPageObject();
        Wrapper<T> wrapper = this.getWrapper();
        return PageData.create(entityMapper.selectPage(page, wrapper),
                mapperFunction);
    }

    public <D> PageData<D> selectPage(BaseMapper<T> entityMapper, Class<D> destinationType) {
        ConversionService conversionService = ContextUtils.getBean(ConversionService.class);
        return selectPage(entityMapper, item -> conversionService.map(item, destinationType));
    }

    /**
     * 获取 MybatisPlus 的 Page 对象，包含页码和页大小。
     *
     * @return age 对象
     */
    @JsonIgnore
    public Page<T> getPageObject() {
        return new Page<T>(pageSearch.getPageNumber(), pageSearch.getPageSize());
    }

    /**
     * 获取 MybatisPlus 的 Wrapper 对象，包含查询条件。
     *
     * @return Wrapper 对象
     */
    @JsonIgnore
    public Wrapper<T> getWrapper() {
        QueryWrapper<T> wrapper = new QueryWrapper<T>();

        // 所有包含 @QueryColumn 注解的字段
        List<FieldInfo> availableFields = getAvailableFields();

        // 根据 PageRequest 对象增加查询条件
        availableFields.forEach(fieldInfo -> {
            appendFromPageRequest(wrapper, fieldInfo, pageSearch);
        });

        // 根据 HttpServletRequest 对象增加查询条件
        HttpServletRequest httpRequest = ServletUtils.getRequest();
        availableFields.forEach(fieldInfo -> {
            appendFromHttpServletRequest(wrapper, fieldInfo, httpRequest);
        });

        // 增加关键字搜索查询条件
        addKeywordSearchCriteria(wrapper, availableFields);

        // 设置排序字段和 升序 / 降序
        setQueryOrder(wrapper, availableFields);

        return wrapper;
    }

    /**
     * 获取所有包含 @QueryColumn 注解的字段
     *
     * 只有包含该注解的字段才可以作为查询条件和排序条件
     *
     * @return 所有包含 @QueryColumn 注解的字段
     */
    private List<FieldInfo> getAvailableFields() {
        List<FieldInfo> availableFields = new ArrayList<>();

        Class<?> clazz = pageSearch.getClass();

        ReflectionUtils.doWithFields(clazz, field -> {
            QueryColumn columnAnnotation = field.getAnnotation(QueryColumn.class);
            if (columnAnnotation != null) {

                // 参数名称，可以使用这个名称从 HttpServletRequest 获取自定义查询条件
                String parameterName = field.getName();

                // SQL栏位名称，使用此名称查询数据库和排序
                String columnName = StringUtils.hasText(columnAnnotation.column()) ?
                        columnAnnotation.column() : toUnderline(parameterName);

                // 字段值，如果字段值是布尔类型，则转换成数据库里面的数值(0和1)
                Object fieldValue = getFieldValue(pageSearch, field.getName());
                if (fieldValue instanceof Boolean) {
                    fieldValue = (boolean) fieldValue ? 1 : 0;
                }

                FieldInfo fieldInfo = new FieldInfo();
                fieldInfo.fieldMember = field;
                fieldInfo.parameterName = parameterName;
                fieldInfo.columnName = columnName;
                fieldInfo.defaultLogic = columnAnnotation.logic();
                fieldInfo.keywordSearch = columnAnnotation.keywordSearch();
                fieldInfo.fieldValue = fieldValue;
                availableFields.add(fieldInfo);
            }
        });

        return availableFields;
    }

    /**
     * 根据 PageRequest 对象增加查询条件
     *
     * @param wrapper
     * @param fieldInfo
     * @param pageSearch
     */
    private void appendFromPageRequest(QueryWrapper<T> wrapper, FieldInfo fieldInfo, PageSearch pageSearch) {
        if (Objects.isNull(fieldInfo.fieldValue)) {
            return;
        }

        String stringVal = ObjectUtils.nullSafeToString(fieldInfo.fieldValue);
        if (!StringUtils.hasText(stringVal)) {
            return;
        }

        addQueryCriteria(wrapper, fieldInfo.defaultLogic, fieldInfo.columnName, fieldInfo.fieldValue);
    }

    /**
     * 根据 HttpServletRequest 对象增加查询条件
     *
     * 例如，Http请求传了一个参数 filterCode=in(1,2,3)
     * 这里包含了如下的意义：
     * 1. 使用字段 code 作为查询字段
     * 2. 使用 in 作为条件
     * 3. 包含三个查询值 1, 2, 3
     *
     * @param wrapper
     * @param fieldInfo
     * @param httpRequest
     */
    private void appendFromHttpServletRequest(QueryWrapper<T> wrapper, FieldInfo fieldInfo, HttpServletRequest httpRequest) {
        String parameterName = "filter" + StringUtils.capitalize(fieldInfo.parameterName);
        String parameterValue = httpRequest.getParameter(parameterName);
        if (!StringUtils.hasText(parameterValue)) {
            return;
        }

        // 参数值应该是类似格式 in(1,2,3)
        String logicStr = null, valueStr = null;
        int leftBracketPos = parameterValue.indexOf("(");
        int rightBracketPos = parameterValue.lastIndexOf(")");
        if (leftBracketPos > 0 && rightBracketPos > 0) {
            logicStr = parameterValue.substring(0, leftBracketPos);
            valueStr = parameterValue.substring(leftBracketPos + 1, rightBracketPos);
        } else {
            logicStr = fieldInfo.defaultLogic.name();
            valueStr = parameterValue;
        }

        // 字符串转成枚举类型
        QueryLogic logic = QueryLogic.valueOf(logicStr);

        // 针对特殊类型做特殊的处理
        switch (logic) {
            // in / notIn 类型，可以有多个值
            case in:
            case notIn:
                List<Object> inValueList = new ArrayList<>();
                String[] inValueItems = StringUtils.tokenizeToStringArray(valueStr, ",");
                Arrays.stream(inValueItems).forEach(item -> inValueList.add(item));
                addQueryCriteria(wrapper, logic, fieldInfo.columnName, inValueList);
                break;

            // between / notBetween 类型，可以有两个值
            case between:
            case notBetween:
                String[] betweenValueItems = StringUtils.tokenizeToStringArray(valueStr, ",");
                String beginValue = betweenValueItems.length > 0 ? betweenValueItems[0] : null;
                String endValue = betweenValueItems.length > 1 ? betweenValueItems[1] : beginValue;
                addQueryCriteria(wrapper, logic, fieldInfo.columnName, new Object[] {beginValue, endValue});
                break;

            default:
                addQueryCriteria(wrapper, logic, fieldInfo.columnName, valueStr);
                break;
        }
    }

    /**
     * 增加查询条件
     *
     * @param wrapper
     * @param queryLogic
     * @param columnName
     * @param columValue
     */
    private void addQueryCriteria(QueryWrapper<T> wrapper, QueryLogic queryLogic, String columnName, Object columValue) {
        switch (queryLogic) {
            case eq:
                wrapper.eq(columnName, columValue);
                break;
            case ne:
                wrapper.ne(columnName, columValue);
                break;
            case ge:
                wrapper.ge(columnName, columValue);
                break;
            case gt:
                wrapper.gt(columnName, columValue);
                break;
            case le:
                wrapper.le(columnName, columValue);
                break;
            case lt:
                wrapper.lt(columnName, columValue);
                break;
            case like:
                wrapper.like(columnName, columValue);
                break;
            case notLike:
                wrapper.notLike(columnName, columValue);
                break;
            case likeLeft:
                wrapper.likeLeft(columnName, columValue);
                break;
            case notLikeLeft:
                wrapper.notLikeLeft(columnName, columValue);
                break;
            case likeRight:
                wrapper.likeRight(columnName, columValue);
                break;
            case notLikeRight:
                wrapper.notLikeRight(columnName, columValue);
                break;
            case between:
                wrapper.between(columnName, ((Object[])columValue)[0], ((Object[])columValue)[1]);
                break;
            case notBetween:
                wrapper.notBetween(columnName, ((Object[])columValue)[0], ((Object[])columValue)[1]);
                break;
            case in:
                wrapper.in(columnName, ((List) columValue).toArray());
                break;
            case notIn:
                wrapper.notIn(columnName, ((List) columValue).toArray());
                break;
            default:
                wrapper.eq(columnName, columValue);
                break;
        }
    }

    /**
     * 设置查询排序
     *
     * @param wrapper
     * @param availableFields
     */
    private void setQueryOrder(QueryWrapper<T> wrapper, List<FieldInfo> availableFields) {
        String orderByColumn;
        OrderDirection direction = null;

        if (StringUtils.hasText(pageSearch.getOrderBy())) {
            orderByColumn = pageSearch.getOrderBy();
            direction = pageSearch.getOrderDirection();
        } else {
            QueryTable tableAnnotation = pageSearch.getClass().getAnnotation(QueryTable.class);
            if (tableAnnotation != null && StringUtils.hasText(tableAnnotation.orderBy())) {
                orderByColumn = tableAnnotation.orderBy();
                direction = tableAnnotation.direction();
            } else {
                orderByColumn = null;
            }
        }

        // 没有找到排序列，立即返回
        if (!StringUtils.hasText(orderByColumn)) {
            return;
        }

        // 排序字段是否是一个查询字段？
        // 只有查询字段才可以作为排序字段
        FieldInfo fieldInfo = availableFields.stream()
                .filter(field ->
                        field.parameterName.equalsIgnoreCase(orderByColumn)
                                || field.columnName.equalsIgnoreCase(orderByColumn)
                )
                .findFirst().orElse(null);
        if (fieldInfo == null) {
            return;
        }

        // 判断是升序还是降序？
        if (direction == OrderDirection.asc) {
            wrapper.orderByAsc(fieldInfo.columnName);
        } else {
            wrapper.orderByDesc(fieldInfo.columnName);
        }
    }

    /**
     * 增加关键字搜索条件
     *
     * @param wrapper
     * @param fields
     */
    private void addKeywordSearchCriteria(QueryWrapper<T> wrapper, List<FieldInfo> fields) {
        String keyword = pageSearch.getSearchKeyword();
        if (!StringUtils.hasText(keyword)) {
            return;
        }

        wrapper.and(wa -> {
            fields.stream().filter(fieldInfo -> fieldInfo.keywordSearch)
                    .forEach(field -> wa.or().like(field.columnName, keyword));
        });
    }

    /**
     * 查询字段信息
     */
    private class FieldInfo {

        /**
         * Java字段
         */
        private Field fieldMember;

        /**
         * 参数名称，可以使用这个名称从 HttpServletRequest 读取自定义查询条件
         */
        private String parameterName;

        /**
         * SQL栏位名称，使用此名称查询数据库
         */
        private String columnName;

        /**
         * 是否支持关键字搜索？
         */
        private Boolean keywordSearch;

        /**
         * 字段查询方式，默认是 eq
         */
        private QueryLogic defaultLogic;

        /**
         * 字段值，如果字段值是布尔类型，则转换成数据库里面的数值(0和1)
         */
        private Object fieldValue;
    }

//    private String convertValueToString(Object value) {
//        Class<?> clazz = value.getClass();
//        if (clazz.isAssignableFrom(Boolean.class)) {
//            return value.toString(); //(Boolean) value ? "1" : "0";
//        } else {
//            return value.toString();
//        }
//    }

    /**
     * 获取字段值，通过 getXXX() 或 isXXX() 方法
     *
     * @param dto
     * @param name
     * @return 字段值
     */
    private Object getFieldValue(Object dto, String name) {
        try {
            Method[] m = dto.getClass().getMethods();
            for (int i = 0; i < m.length; i++) {
                if (("get" + name).toLowerCase().equals(m[i].getName().toLowerCase())
                    || ("is" + name).toLowerCase().equals(m[i].getName().toLowerCase())) {
                    return m[i].invoke(dto);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 驼峰转下划线
     *
     * @param name
     * @return 下划线
     */
    public static String toUnderline(String name) {
        if (name == null) {
            return null;
        }
        int len = name.length();
        StringBuilder res = new StringBuilder(len + 2);
        char pre = 0;
        for (int i = 0; i < len; i++){
            char ch = name.charAt(i);
            if (Character.isUpperCase(ch)) {
                if (pre != '_') {
                    res.append('_');
                }
                res.append(Character.toLowerCase(ch));
            } else {
                res.append(ch);

            }
            pre = ch;
        }
        return res.toString();
    }
}
